package ai.people.netmon.auth.controller;

import ai.people.netmon.framework.constant.NmSystemConstant;
import ai.people.netmon.framework.constant.OAuth2Constant;
import ai.people.netmon.framework.domain.ucenter.request.LoginRequest;
import ai.people.netmon.framework.domain.ucenter.request.LoginUser;
import ai.people.netmon.framework.exception.enums.AuthExceptionEnum;
import ai.people.netmon.framework.exception.type.AuthException;
import ai.people.netmon.framework.model.response.Result;
import ai.people.netmon.framework.utils.AssertUtils;
import ai.people.netmon.framework.utils.ResultUtil;
import ai.people.netmon.utils.CookieUtil;
import ai.people.netmon.utils.StringPool;
import ai.people.netmon.utils.StringUtils;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotEmpty;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@Validated
@RestController
@RequestMapping("/")
@RequiredArgsConstructor
public class AuthController {

    @Value("${auth.clientId}")
    String clientId;
    @Value("${auth.clientSecret}")
    String clientSecret;
    @Value("${auth.cookieMaxAge}")
    int cookieMaxAge;

    private final TokenEndpoint tokenEndpoint;

    private final CheckTokenEndpoint checkTokenEndpoint;

    private final RedisTokenStore redisTokenStore;

    /**
     * 登录
     *
     * @param loginRequest 登录请求
     * @return {@link Result}<{@link ?}>
     */
    @PostMapping("/login")
    public Result<?> login(@Validated @RequestBody LoginRequest loginRequest) throws Exception {
        Map<String, String> param = new HashMap<>();
        param.put(OAuth2Constant.MATE_USERNAME, loginRequest.getUsername());
        param.put(OAuth2Constant.PASSWORD, loginRequest.getPassword());
        param.put(OAuth2Constant.GRANT_TYPE, loginRequest.getGrant_type());
        param.put(OAuth2Constant.CLIENT_ID, StringUtils.isNotEmpty(loginRequest.getClient_id()) ? loginRequest.getClient_id() : clientId);
        param.put(OAuth2Constant.CLIENT_SECRET,  StringUtils.isNotEmpty(loginRequest.getClient_secret()) ? loginRequest.getClient_secret() : clientSecret);
        param.put(OAuth2Constant.VALIDATE_CODE_CODE, loginRequest.getCode());
        return ResultUtil.success(this.tokenRequest(param));
    }


    /**
     * 刷新令牌
     *
     * @param refreshToken 刷新令牌
     * @return {@link Result}<{@link ?}>
     */
    @PostMapping("/refreshToken")
    public Result<?> refreshToken(@NotEmpty(message = "refreshToken为空") @RequestParam("refreshToken") String refreshToken) throws Exception {
        Map<String, String> param = new HashMap<>();
        param.put(OAuth2Constant.REFRESH_TOKEN, refreshToken);
        param.put(OAuth2Constant.GRANT_TYPE, OAuth2Constant.REFRESH_TOKEN);
        return ResultUtil.success(this.tokenRequest(param));
    }


    /**
     * 检查令牌
     *
     * @param token 令牌
     * @return {@link Result}<{@link LoginUser}>
     * @ignore
     */
    @PostMapping("/checkToken")
    public Result<LoginUser> checkToken(@NotEmpty(message = "token为空") @RequestParam("token") String token) {
        Map<String, ?> checkToken = checkTokenEndpoint.checkToken(token);
        AssertUtils.isNotNull(checkToken, AuthExceptionEnum.INVALID_TOKEN);
        LoginUser loginUser = JSON.parseObject(JSON.toJSONString(checkToken), LoginUser.class);
        return ResultUtil.success(loginUser);
    }


    /**
     * 注销
     *
     * @return {@link Result}<{@link ?}>
     *///退出
    @PostMapping("/logout")
    public Result<?> logout() {
        //取出cookie中的用户身份令牌
        String token = getTokenFormCookie();
        //删除redis中的token
        redisTokenStore.removeAccessToken(token);
        //清除cookie
        this.clearCookie(token);
        return ResultUtil.success();
    }


    /**
     * 令牌请求参数
     *
     * @param param 参数
     * @throws HttpRequestMethodNotSupportedException http请求方法不支持异常
     */
    private OAuth2AccessToken tokenRequest(Map<String, String> param) throws HttpRequestMethodNotSupportedException {
        ResponseEntity<OAuth2AccessToken> accessToken = tokenEndpoint.postAccessToken(new UsernamePasswordAuthenticationToken(param.get(OAuth2Constant.CLIENT_ID), param.get(OAuth2Constant.CLIENT_SECRET), AuthorityUtils.commaSeparatedStringToAuthorityList(StringPool.EMPTY)), param);
        String token = Optional.ofNullable(accessToken).map(HttpEntity::getBody).map(OAuth2AccessToken::getValue).orElseThrow(() -> new AuthException(AuthExceptionEnum.AUTHENTICATION_FAILED));
        this.saveCookie(token);
        return accessToken.getBody();
    }


    /**
     * 保存cookie
     *
     * @param token 令牌
     */
    private void saveCookie(String token) {
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        //HttpServletResponse response,String domain,String path, String name, String value, int maxAge,boolean httpOnly
        CookieUtil.addCookie(response, null, "/", NmSystemConstant.AUTH_COOKIE_TOKEN_ID, token, cookieMaxAge, false);

    }


    /**
     * 清晰cookie
     *
     * @param token 令牌
     */
    private void clearCookie(String token) {
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        CookieUtil.addCookie(response, null, "/", NmSystemConstant.AUTH_COOKIE_TOKEN_ID, token, 0, false);
    }


    /**
     * 获得令牌
     *
     * @return {@link String}
     */
    private String getTokenFormCookie() {
        String authCookieTokenId = NmSystemConstant.AUTH_COOKIE_TOKEN_ID;
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Map<String, String> map = CookieUtil.readCookie(request, authCookieTokenId);
        if (map.get(authCookieTokenId) != null) {
            return map.get(authCookieTokenId);
        }
        return null;
    }
}
